﻿using System;

namespace Monkeyspeak
{
    [Serializable]
    public class VariableIsConstantException : Exception
    {
        public VariableIsConstantException()
        {
        }

        public VariableIsConstantException(string message)
            : base(message)
        {
        }

        public VariableIsConstantException(string message, Exception inner)
            : base(message, inner)
        {
        }

        protected VariableIsConstantException(
          System.Runtime.Serialization.SerializationInfo info,
          System.Runtime.Serialization.StreamingContext context)
            : base(info, context) { }
    }

    [Serializable]
    [CLSCompliant(true)]
    public class Variable : IEquatable<Variable>
    {
        public bool Equals(Variable other)
        {
            return isConstant.Equals(other.isConstant) && Equals(value, other.value) && string.Equals(name, other.name);
        }

        /// <summary>
        /// Returns the hash code for this instance.
        /// </summary>
        /// <returns>
        /// A 32-bit signed integer that is the hash code for this instance.
        /// </returns>
        public override int GetHashCode()
        {
            unchecked
            {
                int hashCode = isConstant.GetHashCode();
                hashCode = (hashCode * 397) ^ (value != null ? value.GetHashCode() : 0);
                hashCode = (hashCode * 397) ^ (name != null ? name.GetHashCode() : 0);
                return hashCode;
            }
        }

        public static readonly Variable NoValue = new Variable("%none", null);

        private bool isConstant;

        public bool IsConstant
        {
            get
            {
                return isConstant;
            }
            set
            {
                isConstant = value;
            }
        }

        private object value;

        public object Value
        {
            get { return value; }
            set
            {
                // removed Value = as it interfered with page.setVariable - Gerolkae
                if (CheckType(value) == false) throw new TypeNotSupportedException(value.GetType().Name +
                " is not a supported type. Expecting string or double.");

                if (IsConstant == false)
                    this.value = value;
                else throw new VariableIsConstantException("Attempt to assign a _value to constant \"" + Name + "\"");
            }
        }

        public void Set(string str)
        {
            Value = str;
        }

        public void Set(double num)
        {
            Value = num;
        }

        private string name;

        public string Name
        {
            get
            {
                return name;
            }
            internal set
            {
                name = value;
            }
        }

        // Variable var = new variable();
        // Preset reader.readvariable with default data
        // Needed for Conditions  checking Variables that haven't been defined yet.
        // -Gerolkae
        private Variable()
        {
            isConstant = false;
            name = "%none";
            value = null;
        }

        internal Variable(string Name, object value, bool constant = false)
        {
            isConstant = constant;
            name = Name;
            this.value = value;
        }

        public void ForceAssignValue(object _value)
        {
            value = _value;
        }

        private bool CheckType(object _value)
        {
            if (_value == null) return true;

            return _value is string ||
                   _value is double;
        }

        /// <summary>
        /// Returns a const identifier if the variable is constant followed by name,
        /// <para>otherwise just the name is returned.</para>
        /// </summary>
        /// <returns></returns>
        public override string ToString()
        {
            return ((IsConstant) ? "const " : "") + Name + " = " + ((Value == null) ? "null" : Value.ToString());
        }

        /// <summary>
        ///
        /// </summary>
        /// <param name="asConstant">Clone as Constant</param>
        /// <returns></returns>
        public Variable Clone(bool asConstant = false)
        {
            return new Variable(Name, Value, asConstant);
        }

        public static bool operator ==(Variable varA, Variable varB)
        {
            return varA.Value == varB.Value;
        }

        public static bool operator !=(Variable varA, Variable varB)
        {
            return varA.Value != varB.Value;
        }

        public static Variable operator +(Variable varA, double num)
        {
            varA.Value = varA.AsDouble() + num;
            return varA;
        }

        public static Variable operator -(Variable varA, double num)
        {
            varA.Value = varA.AsDouble() - num;
            return varA;
        }

        public static Variable operator *(Variable varA, double num)
        {
            if (varA.Value is double)
            {
                varA.Value = varA.AsDouble() * num;
            }
            return varA;
        }

        public static Variable operator /(Variable varA, double num)
        {
            varA.Value = varA.AsDouble() / num;
            return varA;
        }

        public static Variable operator +(Variable varA, string str)
        {
            varA.Value = varA.AsString() + str;
            return varA;
        }

        public static implicit operator string(Variable var)
        {
            return var.AsString();
        }

        public static implicit operator double(Variable var)
        {
            double num;
            Double.TryParse(var.Value as string, out num);
            return num;
        }

        public override bool Equals(object obj)
        {
            if (ReferenceEquals(null, obj))
            {
                return false;
            }
            return obj is Variable && Equals((Variable)obj);
        }

        public double AsDouble()
        {
            double num = 0;
            if (value == null)
            {
                value = 0d;
            }
            num = Convert.ToDouble(value);
            return num;
        }

        public string AsString()
        {
            return Convert.ToString(Value);
        }
    }
}